﻿using System;
using System.ComponentModel.Composition;
using System.Threading;
using Jacobi.Seesmic.FeedReader.Model;
using Jacobi.Seesmic.FeedReader.Utilities;
using Seesmic.Sdp.Extensibility;
using Seesmic.Sdp.Utils;

namespace Jacobi.Seesmic.FeedReader
{
    /// <summary>
    /// The DataProvider manages all the <see cref="FeedSource"/>s and publishes <see cref="ITimelineItem"/>s.
    /// </summary>
    /// <remarks>
    /// A background timer refreshes all <see cref="FeedSource"/>s every two minutes.
    /// </remarks>
    [Export(typeof(IDataProvider))]
    public sealed class DataProvider : IDataProvider, IDisposable
    {
        /// <summary>A unique Id for this DataProvider instance.</summary>
        private static readonly Guid UniqueId = new Guid("1F5DE319-730A-4EFE-994E-39613D019E2A");
        /// <summary>Two minutes (in milliseconds) is the interval period for the refresh timer.</summary>
        private const int RefreshFrequency = 120000;

        private Timer _timer;
        private CollectionObserver<FeedInfo> _feedObserver;
        private FeedManagerCollection _feedManagers;
        private FeedManagerFactory _feedMgrFactory;

        /// <summary>
        /// Gets the <see cref="FeedManagerFactory"/> used to create new <see cref="FeedManager"/> instances.
        /// </summary>
        private FeedManagerFactory FeedManagerFactory
        {
            get
            {
                if (_feedMgrFactory == null)
                {
                    _feedMgrFactory = new FeedManagerFactory(this);
                }

                return _feedMgrFactory;
            }
        }

        /// <summary>
        /// Gets or sets an implementation of the <see cref="ILogService"/> provided by the host.
        /// </summary>
        /// <remarks>Injected by MEF.</remarks>
        [Import(typeof(ILogService))]
        public ILogService LogService { get; set; }

        /// <summary>
        /// Gets the unique Id for this DataProvider instance.
        /// </summary>
        public Guid Id
        {
            get { return UniqueId; }
        }

        /// <summary>
        /// Gets an implementation for an action button that is displayed in the sidebar.
        /// </summary>
        public ISidebarAction SidebarAction
        {
            get { return FeedTimeline.Current.SidebarAction; }
        }

        /// <summary>
        /// Called on startup to initialize and start the DataProvider.
        /// </summary>
        public void Start()
        {
            Cleanup();

            _feedManagers = FeedManagerFactory.CreateAll(Plugin.FeedInfos);

            _feedObserver = new CollectionObserver<FeedInfo>(Plugin.FeedInfos);
            _feedObserver.Observe(OnFeedAdded, OnFeedRemoved);

            foreach (var feedManager in _feedManagers)
            {
                feedManager.Start();
            }

            _timer = new Timer((userState) => SynchronizationHelper.Post(UpdateAllFeedSources),
                null, RefreshFrequency, RefreshFrequency);
        }

        /// <summary>
        /// Call by the host to stop the DataProvider from publishing <see cref="ITimelineItem"/>s.
        /// </summary>
        public void Stop()
        {
            Cleanup();
        }

        /// <summary>
        /// Helper method to reset and clear internal state.
        /// </summary>
        private void Cleanup()
        {
            if (_timer != null)
            {
                _timer.Dispose();
                _timer = null;
            }

            if (_feedObserver != null)
            {
                _feedObserver.Dispose();
                _feedObserver = null;
            }

            if (_feedManagers != null)
            {
                foreach (var feedManager in _feedManagers)
                {
                    feedManager.Stop();
                }

                _feedManagers = null;
            }

            _feedMgrFactory = null;
        }

        /// <summary>
        /// Iterates through all <see cref="FeedSource"/>s and starts a (down)load.
        /// </summary>
        private void UpdateAllFeedSources()
        {
            foreach (var feedManager in _feedManagers)
            {
                feedManager.BeginLoadFeedSource();
            }
        }

        /// <summary>
        /// Is called when a <see cref="FeedInfo"/> is added to the <see cref="Plugin.FeedInfos"/> collection.
        /// </summary>
        /// <param name="feedInfo">The newly added item.</param>
        /// <remarks>
        /// A new <see cref="FeedSource"/> is created and initialized.
        /// </remarks>
        private void OnFeedAdded(FeedInfo feedInfo)
        {
            var feedManager = FeedManagerFactory.CreateFeedManager(feedInfo);

            _feedManagers.Add(feedManager);

            feedManager.Start();
        }

        /// <summary>
        /// Is called when a <see cref="FeedInfo"/> is removed from the <see cref="Plugin.FeedInfos"/> collection.
        /// </summary>
        /// <param name="feedInfo">The removed item.</param>
        /// <remarks>
        /// All <see cref="ITimelineItem"/>s are removed as well as the <see cref="FeedSource"/> itself.
        /// </remarks>
        private void OnFeedRemoved(FeedInfo feedInfo)
        {
            string key = feedInfo.Url;

            if (_feedManagers.Contains(key))
            {
                var feedManager = _feedManagers[key];

                if (feedManager != null)
                {
                    feedManager.Stop();

                    _feedManagers.Remove(feedManager);
                }
            }
        }

        /// <summary>
        /// Adds the <paramref name="item"/> to the timeline after it has been processed.
        /// </summary>
        /// <param name="item">Must not be null.</param>
        internal void Add(TimelineItemContainer item)
        {
            OnItemAdded(item);
        }

        /// <summary>
        /// Removes the <paramref name="item"/> from the timeline.
        /// </summary>
        /// <param name="item">Must not be null.</param>
        internal void Remove(TimelineItemContainer item)
        {
            OnItemRemoved(item);
        }

        /// <summary>
        /// Raises the <see cref="ItemAdded"/> event when a new item is published.
        /// </summary>
        /// <param name="timelineItem">Must not be null.</param>
        private void OnItemAdded(TimelineItemContainer timelineItem)
        {
            timelineItem.ThrowIfArgumentIsNull("timelineItem");

            var handler = ItemAdded;

            if (handler != null)
            {
                var args = new TimelineItemEventArgs(timelineItem);
                handler(this, args);
            }
        }

        /// <summary>
        /// Is raised when a new item is published.
        /// </summary>
        public event EventHandler<TimelineItemEventArgs> ItemAdded;

        /// <summary>
        /// Raises the <see cref="ItemRemoved"/> event when an item is unpublished.
        /// </summary>
        /// <param name="timelineItem">Must not be null.</param>
        private void OnItemRemoved(TimelineItemContainer timelineItem)
        {
            timelineItem.ThrowIfArgumentIsNull("timelineItem");

            var handler = ItemRemoved;

            if (handler != null)
            {
                var args = new TimelineItemEventArgs(timelineItem);
                handler(this, args);
            }
        }

        /// <summary>
        /// Is raised when a old item is unpublished.
        /// </summary>
        public event EventHandler<TimelineItemEventArgs> ItemRemoved;

        /// <summary>
        /// Is called by the host when the item was processed by all registered
        /// <see cref="T:Seesmic.Sdp.Extensibility.ITimelineItemProcessor"/>s.
        /// </summary>
        /// <param name="result">The result of the processing of the published item.</param>
        public void OnItemProcessed(ProcessingResult result)
        {
            if (result != null && !result.Blocked)
            {
                FeedTimeline.Current.Add(result.TimelineItemContainer);
            }
        }

        /// <summary>
        /// Cleans up the DataProvider instance.
        /// </summary>
        public void Dispose()
        {
            Cleanup();
        }
    }
}